package org.hagen.mmia.yars.agent;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.hagen.mmia.yars.agent.utils.FloatTreeMap;
import org.hagen.mmia.yars.agent.utils.lpSolver;
import org.hagen.mmia.yars.client.User;
import org.hagen.mmia.yars.agent.utils.DocumentTreeMap;

/*
 * collaboration support
 */
public class CollabSupport {

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Enable tracing for this module
	static final boolean	verbose = false;

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Modes: baselines and ILP approach
	public final static int PRP		  	 = 1;
	public final static int ILP 	  	 = 2;
	public final static int RR_SPLIT	 = 3;
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	// ILP Solver and shared set of result-documents
	public  DocumentTreeMap		sharedResult	= null;
	private lpSolver 			solver			= null;
	private int 				mode			= -1;

	@SuppressWarnings("unchecked")
	////////////////////////////////////////////////////////////////////////////////////////////////
	// Result pages extracted for users
	ArrayList<String>[] resultBuffer			= null;
	
	////////////////////////////////////////////////////////////////////////////////////////////////

	/*
	 * destructor (artificial)
	 */
	public void destruct() {
		sharedResult.clear();
		sharedResult = null;
		
		resultBuffer		 = null;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * constructor
	 */
	public void initialize( ) {
		sharedResult = new DocumentTreeMap();
		resultBuffer = (ArrayList<String>[])new ArrayList[User.MAX_NO_OF_USERS];
		for( int i=0; i<User.MAX_NO_OF_USERS; i++ ) {
			resultBuffer[i] = new ArrayList<String>();
		}
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * constructor
	 */
	public CollabSupport( ) {
		initialize();
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * create the shared result for all users, i.e. shared set of result documents
	 */
	public void buildSharedResultSet( ArrayList<User> users, int mode ) {

		this.mode = mode;
		sharedResult.clear();
		
		for( User u : users ) {
			ResultList lr = u.getPastResult();
			
			for( String ID : lr.retrievedSet ) {
				
				// use CombSUM: add probabilities of relevance (actually the scores)
				double score = u.getProbability( ID );
				sharedResult.add( ID, score );
			}
		}

		if( verbose ) {
			System.out.println( "Documents in queue: " + sharedResult.size() );
			
			System.out.print( "Result queue: " );
			for ( Map.Entry<String,Double> entry : sharedResult.entrySet() ) {
				String docID   = (String)entry.getKey();
				System.out.print( " ( " + docID + " " + (double)entry.getValue() + " ) ");
			}
			System.out.println( "" );
		}
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 *  list of terms sorted by probability of relevance for a given user 
	 */
	public TreeMap<String,Double> getQueueSortedForUser( User user ) {

		FloatTreeMap probability = new FloatTreeMap();

		for ( Map.Entry<String,Double> entry : sharedResult.entrySet() ) {
            
			String docID   = (String)entry.getKey();
			
			Double userProbability = user.getProbability( docID );
            
            probability.add( docID, userProbability );
		}

		ValueComparator bvc =  new ValueComparator(probability);
		TreeMap<String,Double> sorted_map = new TreeMap<String,Double>(bvc);
		for ( Entry<Object, Double> entry : probability.entrySet() ) {
			sorted_map.put((String)entry.getKey(),entry.getValue());
		}

		if( verbose ) {
			System.out.print( "Probability queue for user: " + user.ID );
			for ( Entry<String, Double> entry : sorted_map.entrySet() ) {

				String docID   = (String)entry.getKey();
				System.out.println( " ( " + docID + " " + (double)entry.getValue() + " ) ");
			}
			System.out.println( "" );
		}

		return sorted_map;
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 *  cluster of terms sorted by probability of usefulness for user 
	 */
	public TreeMap<String,Double> getClusterSortedForUser( User user, ArrayList<String> docs ) {
		
		FloatTreeMap probability = new FloatTreeMap();
		
		for ( String docID : docs ) {
			
			Double userProbability = user.getProbability( docID );
            probability.add( docID, userProbability );
		}

		ValueComparator bvc =  new ValueComparator(probability);
		TreeMap<String,Double> sorted_map = new TreeMap<String,Double>(bvc);
		for ( Entry<Object, Double> entry : probability.entrySet() ) {
			sorted_map.put((String)entry.getKey(),entry.getValue());
		}

		if( verbose ) {
		
			System.out.print( "Probability queue: " );
			for ( Entry<String, Double> entry : sorted_map.entrySet() ) {
				
				String docID   = (String)entry.getKey();
				System.out.println( " ( " + docID + " " + (double)entry.getValue() + " ) ");
			}
			System.out.println( "" );
		}
		
		return sorted_map;
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * solver for clustering approaches
	 */
	public void solve( ArrayList<User> users ) {
		
		switch( mode ) {
		
		case PRP: {
			// nothing to do
		}
		break;
		
		case ILP: {
				// create ILP solver
				solver = new lpSolver();
		
				// get highest probability
				Double probNorm[] = new Double[users.size()];
				for( User user : users ) {
					
					probNorm[user.ID] = User.MIN_SCORE;
					
					for ( Map.Entry<String,Double> entry : sharedResult.entrySet() ) {
			
						String docID   = (String)entry.getKey();
						Double userProbability = user.getProbability( docID );
			            
			            if( userProbability > probNorm[user.ID] )
			            	probNorm[user.ID] = new Double( userProbability );
					}
				}

				// build matrix for LP: rows are documents, columns are users
				Iterator<Entry<String, Double>> it = sharedResult.entrySet().iterator();
	
				int ucnt = 0;
				int dcnt = 0;
				while( it.hasNext() ) {
					Entry<String, Double> e = it.next();
					String docID = (String)e.getKey();

					ucnt = 0;
					for( User user : users ) {

						Double prob = user.getProbability( docID ) / probNorm[user.ID];
						solver.addObjective( ucnt, dcnt, prob );

						if( verbose ) {
							System.out.println( docID + " [" + user.ID + "] (" + prob + ")" );
						}
						ucnt++;
					}
	
		            dcnt++;
				}
				solver.setObjective();

				// set capacity for each user
				ucnt = 0;
				for( @SuppressWarnings("unused") User user : users ) {
					
					solver.setCapacity( ucnt, sharedResult.size(), User.MAX_HITS_PER_USER );
					
					ucnt++;
				}
		
				// set maximum visits for each document
				dcnt = 0;
				Iterator<Entry<String, Double>> itd = sharedResult.entrySet().iterator();
				while( itd.hasNext() ) {
		            itd.next();
		            solver.setVisits( ucnt, dcnt, User.MAX_OVERALL_VISITS );
		            dcnt++;
				}
		
				solver.solve( ucnt, dcnt );
			}
			break;

		case RR_SPLIT: {

				FloatTreeMap probability = new FloatTreeMap();
				
				for ( Entry<String, Double> entry : sharedResult.entrySet() ) {
					
					Double score = entry.getValue();
		            probability.add( entry.getKey(), score );
				}
	
				ValueComparator bvc =  new ValueComparator(probability);
				TreeMap<String,Double> sorted_map = new TreeMap<String,Double>(bvc);
				for ( Entry<Object, Double> entry : probability.entrySet() ) {
					sorted_map.put((String)entry.getKey(),entry.getValue());
				}
			
				// assumption: both users entered the same query
				Iterator<Entry<String, Double>> itd = sorted_map.entrySet().iterator();

				int teamSize = users.size();
				int cnt	     = 0;
				
				while( itd.hasNext() ) {

					String docID = itd.next().getKey();
		            
					int modulo = cnt % teamSize;
					cnt++;
					resultBuffer[modulo].add(docID);
				}				
			}
			break;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * 	
	 */
	public Iterator<Entry<String, Double>> getIterator() {
		return sharedResult.entrySet().iterator();
	}
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	
	/*
	 * 	
	 */
	public ArrayList<String> fetch( User user ) {

		ArrayList<String> result = new ArrayList<String>();

		switch( mode ) {
		
		/*
		 * 
		 */
		case PRP: {
				TreeMap<String, Double> docs = getQueueSortedForUser( user );
				Iterator<Entry<String, Double>> it = docs.entrySet().iterator();
	
				int dcnt = 0;
				while( it.hasNext() ) {
	
		            dcnt++;
		            if ( dcnt > User.MAX_HITS_PER_USER ) {
		            	break;
		            }
	
		            Entry<String, Double> entry = it.next();
		            String docID = (String)entry.getKey();
	
		            result.add( docID );
				}
			}
			break;
			
		/*
		 * 
		 */
		case ILP: {
				int dcnt = 0;
	
				Iterator<Entry<String, Double>> it = getIterator();
				
				while (it.hasNext()) {
					Entry<String, Double> entry = it.next();
					Number n = solver.get( user.ID, dcnt );
				
					if( n.intValue() == 1 ) {
						result.add( ( String ) entry.getKey() );
					}
					dcnt++;
				}
			}
			break;
		
		/*
		 * 
		 */
		case RR_SPLIT: {
				TreeMap<String, Double> docs = null;
				
				docs = getClusterSortedForUser( user, resultBuffer[user.ID] );
				Iterator<Entry<String, Double>> it = docs.entrySet().iterator();
				
				int dcnt = 0;
				while( it.hasNext() ) {
	
		            dcnt++;
		            if( dcnt > User.MAX_HITS_PER_USER ) {
		            	break;
		            }
	
		            Entry<String, Double> entry = it.next();
		            String doc = (String)entry.getKey();
	
		            result.add( doc );
				}
			}
			break;
		}

		return result;
	}
}

/*
 * 
 */
class ValueComparator implements Comparator<Object> {

    Map<Object, Double> base;
    public ValueComparator(FloatTreeMap probability) {
        this.base = probability;
    }

    // Note: this comparator imposes orderings that are inconsistent with equals.    
	public int compare(Object a, Object b) {
        if (base.get(a) >= base.get(b)) {
            return -1;
        } else {
            return 1;
        } // returning 0 would merge keys
    }
}
